package com.android.looper;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.SurfaceView;

import java.util.List;

/**
 * LoopSurfaceView is a final SurfaceView. You put it in your layout and use the
 * setAdapter(LoopSurfaceViewAdapter) method to override methods that is your
 * concern, like draw().
 *
 * @author JoanZap
 */
public final class LoopSurfaceView extends SurfaceView {

    private static final String TAG = LoopSurfaceView.class.getSimpleName();
    private static final int INVALID_POINTER_ID = -1;

    private InnerThread innerThread;
    private LoopAdapter adapter;
    private ScaleGestureDetector mScaleDetector;
    private int mActivePointerId = INVALID_POINTER_ID;

    private PointF mCenter = new PointF(0, 0);
    private PointF mLastCenter = new PointF(0, 0);
    private PointF mTouchedPoint = new PointF(0, 0);
    private float mScaleFactor = 1.f;

    public LoopSurfaceView(Context context) {
        super(context);
        // Create our ScaleGestureDetector
        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    }

    public LoopSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // Create our ScaleGestureDetector
        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    }

    public LoopSurfaceView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // Create our ScaleGestureDetector
        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        mScaleDetector.onTouchEvent(event);
        boolean touched = false;

        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                mActivePointerId = event.getPointerId(0);
                mTouchedPoint = new PointF(event.getX(0), event.getY(0));
                mLastCenter = mCenter;
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                if (!mScaleDetector.isInProgress()) {
                    final int pointerIndex = event.findPointerIndex(mActivePointerId);
                    PointF touchedNow = new PointF(event.getX(pointerIndex), event.getY(pointerIndex));
                    mCenter = new PointF(mLastCenter.x + (touchedNow.x - mTouchedPoint.x),
                            mLastCenter.y + (touchedNow.y - mTouchedPoint.y));
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                mActivePointerId = INVALID_POINTER_ID;
                touched = false;
                break;
            }
            case MotionEvent.ACTION_POINTER_DOWN: {
                touched = false;
                break;
            }
            case MotionEvent.ACTION_POINTER_UP: {
                final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
                        >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
                final int pointerId = event.getPointerId(pointerIndex);
                if (pointerId == mActivePointerId) {
                    // This was our active pointer going up. Choose a new
                    // active pointer and adjust accordingly.
                    final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
//                    mLastTouchX = ev.getX(newPointerIndex);
//                    mLastTouchY = ev.getY(newPointerIndex);
                    mActivePointerId = event.getPointerId(newPointerIndex);
                }
                touched = false;
                break;
            }
            case MotionEvent.ACTION_CANCEL: {
                mActivePointerId = INVALID_POINTER_ID;
                touched = false;
                break;
            }
            case MotionEvent.ACTION_OUTSIDE: {
                touched = false;
                break;
            }
            default:
        }

        return true;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        // TODO
    }

    /**
     * Start the update/draw loop.
     */
    public void play() {

        if (adapter == null)
            throw new IllegalArgumentException("Can't run without adapter.");

        // If the thread is running the surface view has been started before
        // and has never been stopped.
        if (innerThread != null && innerThread.isRunning()) {
            throw new IllegalStateException("You can't call play() twice.");
        }

        // Start the inner thread
        innerThread = new InnerThread();
        innerThread.start();

    }

    /**
     * Pause the loop. You can start it again with play().
     */
    public void pause() {
        // Stop the inner thread
        innerThread.cleanStop();
        innerThread = null;
    }

    /**
     * Set the adapter for this surface view.
     */
    public void setAdapter(LoopAdapter adapter) {
        this.adapter = adapter;
    }

    private class InnerThread extends Thread {

        private volatile boolean shouldStop = false;

        @Override
        public void run() {
            while (!shouldStop) {
                Canvas canvas = null;
                try {
                    canvas = getHolder().lockCanvas();
                    if (canvas != null) {
                        adapter.update(0);
                        adapter.drawBackground(canvas);
                        renderObjects(adapter.getDrawableObjects(), canvas);
                    }
                } finally {
                    if (canvas != null) getHolder().unlockCanvasAndPost(canvas);
                }
            }
        }

        private void renderObjects(List<DrawableObject> drawableObjects, Canvas canvas) {
            for (DrawableObject drawableObject : drawableObjects) {
                if (mCenter == null) {
                    mCenter = new PointF(canvas.getWidth() / 2,
                            canvas.getHeight() / 2);
                }
                drawableObject.setCenter(mCenter);
                drawableObject.setScaleFactor(mScaleFactor);
//                Log.i(TAG, "ScaleFactor " + mScaleFactor);
                drawableObject.draw(canvas);
            }
        }

        public boolean isRunning() {
            return !shouldStop;
        }

        public void cleanStop() {
            shouldStop = true;
        }

    }

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            mScaleFactor *= detector.getScaleFactor();

            // Don't let the object get too small or too large.
            mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));

            invalidate();
            return true;
        }
    }

}
